/***************************************************************************
 # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
 # are met:
 #  * Redistributions of source code must retain the above copyright
 #    notice, this list of conditions and the following disclaimer.
 #  * Redistributions in binary form must reproduce the above copyright
 #    notice, this list of conditions and the following disclaimer in the
 #    documentation and/or other materials provided with the distribution.
 #  * Neither the name of NVIDIA CORPORATION nor the names of its
 #    contributors may be used to endorse or promote products derived
 #    from this software without specific prior written permission.
 #
 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY
 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 # PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **************************************************************************/
#include "RenderGraphUI.h"
#include "Utils/Algorithm/DirectedGraphTraversal.h"
#include "Utils/Scripting/Scripting.h"

#include <imgui.h>
#include <imgui_internal.h>
#include <imguinodegrapheditor/imguinodegrapheditor.h>

#include <fstream>

namespace Falcor
{
const float kUpdateTimeInterval = 2.0f;
const float kPinRadius = 7.0f;

static const float kTimeTillPopup = 2.0f;
static const uint32_t kPinColor = 0xFFFFFFFF;
static const uint32_t kEdgesColor = 0xFFFFFFFF;
static const uint32_t kAutoResolveEdgesColor = 0xFF0104FF;
static const uint32_t kGraphOutputsColor = 0xAF0101FF;
static const uint32_t kExecutionEdgeColor = 0xFF0AF1FF;
static const std::string kInPrefix = "##inEx_ ";
static const std::string kOutPrefix = "##outEx_";

class RenderGraphUI::NodeGraphEditorGui : public ImGui::NodeGraphEditor
{
public:
    NodeGraphEditorGui(RenderGraphUI* pRenderGraphUI) : mpRenderGraphUI(pRenderGraphUI) {}

    // call on beginning a new frame
    void setCurrentGui(Gui* pGui) { mpGui = pGui; }

    Gui* getCurrentGui() const
    {
        FALCOR_ASSERT(mpGui);
        return mpGui;
    }

    void reset() { inited = false; }

    float2 getOffsetPos() const { return {offset.x, offset.y}; }

    ImGui::NodeLink& getLink(int32_t index) { return links[index]; }

    void setLinkColor(uint32_t index, uint32_t col) { links[index].LinkColor = col; }

    RenderGraphUI* getRenderGraphUI() { return mpRenderGraphUI; }

    void setPopupNode(ImGui::Node* pFocusedNode) { mpFocusedNode = pFocusedNode; }

    ImGui::Node* getPopupNode() { return mpFocusedNode; }

    void setPopupPin(uint32_t pinIndex, bool isInput)
    {
        if (ImGui::IsAnyMouseDown())
        {
            mPopupPinHoverTime = 0.0f;
            mPinIndexToDisplay = -1;
        }

        if ((pinIndex != -1)) //&& pinIndex != mPinIndexToDisplay)
        {
            std::chrono::system_clock::time_point thisTime = std::chrono::system_clock::now();
            float timeDiff = (std::chrono::duration<float>(thisTime - mLastTime)).count();
            if (timeDiff < kTimeTillPopup)
                mPopupPinHoverTime += timeDiff;
            mLastTime = thisTime;
            if (mPopupPinHoverTime < kTimeTillPopup)
                return;
        }

        if (mPopupPinHoverTime >= kTimeTillPopup)
            mPopupPinHoverTime = 0.0f;
        mPinIndexToDisplay = pinIndex;
        mPopupPinIsInput = isInput;
    }

    void deselectPopupPin()
    {
        std::chrono::system_clock::time_point thisTime = std::chrono::system_clock::now();
        float timeDiff = (std::chrono::duration<float>(thisTime - mLastTime)).count();
        if (timeDiff < kTimeTillPopup)
            mNothingHoveredTime += timeDiff;
        mLastTimeDeselect = thisTime;

        if (mNothingHoveredTime >= kTimeTillPopup)
        {
            mNothingHoveredTime = 0.0f;
            mPinIndexToDisplay = static_cast<uint32_t>(-1);
        }
    }

    uint32_t getPopupPinIndex() const { return mPinIndexToDisplay; }

    bool isPopupPinInput() const { return mPopupPinIsInput; }

    ImGui::Node*& getNodeFromID(uint32_t nodeID) { return mpIDtoNode[nodeID]; }

    // wraps around creating link to avoid setting static flag
    uint32_t addLinkFromGraph(
        ImGui::Node* inputNode,
        int input_slot,
        ImGui::Node* outputNode,
        int output_slot,
        bool checkIfAlreadyPresent = false,
        ImU32 col = ImColor(200, 200, 100)
    )
    {
        // tell addLink to call a different callback func
        auto oldCallback = linkCallback;
        linkCallback = setLinkFromGraph;

        bool insert = addLink(inputNode, input_slot, outputNode, output_slot, checkIfAlreadyPresent, col);
        linkCallback = oldCallback;
        return insert ? (links.size() - 1) : uint32_t(-1);
    }

    ImGui::Node* addAndInitNode(
        int nodeType,
        const std::string& name,
        const std::string& outputsString,
        const std::string& inputsString,
        uint32_t guiNodeID,
        RenderPass* pCurrentRenderPass,
        const ImVec2& pos = ImVec2(0, 0)
    );

    void setNodeCallbackFunc(NodeCallback cb, void* pUserData)
    {
        mpCBUserData = pUserData;
        setNodeCallback(cb);
    }

    // callback function defined in derived class definition to access data without making class public to the rest of Falcor
    static void setNode(ImGui::Node*& node, ImGui::NodeGraphEditor::NodeState state, ImGui::NodeGraphEditor& editor);
    // callback for ImGui setting link between to nodes in the visual interface
    static void setLinkFromGui(ImGui::NodeLink& link, ImGui::NodeGraphEditor::LinkState state, ImGui::NodeGraphEditor& editor);
    static void setLinkFromGraph(ImGui::NodeLink& link, ImGui::NodeGraphEditor::LinkState state, ImGui::NodeGraphEditor& editor);
    static ImGui::Node* createNode(int, const ImVec2& pos, const ImGui::NodeGraphEditor&);

    RenderGraphUI* mpRenderGraphUI;

private:
    friend class RenderGraphNode;

    float mPopupPinHoverTime = 0.0f;
    float mNothingHoveredTime = 0.0f;
    std::chrono::system_clock::time_point mLastTime = std::chrono::system_clock::now();
    std::chrono::system_clock::time_point mLastTimeDeselect = std::chrono::system_clock::now();
    Gui* mpGui = nullptr;
    void* mpCBUserData = nullptr;
    ImGui::Node* mpFocusedNode = nullptr;
    uint32_t mPinIndexToDisplay = uint32_t(-1);
    bool mPopupPinIsInput = false;
    std::unordered_map<uint32_t, ImGui::Node*> mpIDtoNode;
};

class RenderGraphUI::RenderGraphNode : public ImGui::Node
{
public:
    bool mDisplayProperties;
    bool mOutputPinConnected[IMGUINODE_MAX_OUTPUT_SLOTS];
    bool mInputPinConnected[IMGUINODE_MAX_INPUT_SLOTS];
    RenderPass* mpRenderPass;

    bool pinIsConnected(uint32_t id, bool isInput)
    {
        FALCOR_ASSERT(isInput ? id < IMGUINODE_MAX_INPUT_SLOTS : id < IMGUINODE_MAX_OUTPUT_SLOTS);
        return isInput ? mInputPinConnected[id] : mOutputPinConnected[id];
    }

    void setPinColor(uint32_t color, uint32_t index, bool isInput = false)
    {
        if (isInput)
            inputColors[index] = color;
        else
            outputColors[index] = color;
    }

    std::string getInputName(uint32_t index) { return InputNames[index]; }

    std::string getOutputName(uint32_t index) { return OutputNames[index]; }

    ImGui::FieldInfoVector& getFields() { return fields; }

    // Allow the renderGraphUI to set the position of each node
    void setPos(const float2& pos)
    {
        Pos.x = pos.x;
        Pos.y = pos.y;
    }

    float2 getPos() { return {Pos.x, Pos.y}; }

    // render Gui within the nodes
    static bool renderUI(ImGui::FieldInfo& field)
    {
        // with this library there is no way of modifying the positioning of the labels on the node
        // manually making labels to align correctly from within the node

        // grab all of the fields again
        RenderGraphNode* pCurrentNode = static_cast<RenderGraphNode*>(field.userData);
        RenderGraphUI::NodeGraphEditorGui* pGraphEditorGui =
            static_cast<RenderGraphUI::NodeGraphEditorGui*>(&pCurrentNode->getNodeGraphEditor());
        if (pGraphEditorGui->isInited())
            return false;

        Gui* pGui = pGraphEditorGui->getCurrentGui();
        RenderPass* pRenderPass = static_cast<RenderPass*>(pCurrentNode->mpRenderPass);
        int32_t paddingSpace = std::max(pCurrentNode->OutputsCount, pCurrentNode->InputsCount) / 2 + 1;
        ImVec2 oldScreenPos = ImGui::GetCursorScreenPos();
        ImVec2 currentScreenPos{
            pGraphEditorGui->offset.x + pCurrentNode->Pos.x * ImGui::GetCurrentWindow()->FontWindowScale,
            pGraphEditorGui->offset.y + pCurrentNode->Pos.y * ImGui::GetCurrentWindow()->FontWindowScale};
        ImVec2 pinRectBoundsOffsetx{-kPinRadius * 2.0f, kPinRadius * 4.0f};
        float slotNum = 1.0f;
        float pinOffsetx = kPinRadius * 2.0f;
        uint32_t pinCount = static_cast<uint32_t>(pCurrentNode->InputsCount);
        bool isInputs = true;

        std::string dummyText;
        dummyText.resize(32, ' ');
        ImGui::TextUnformatted(dummyText.c_str());
        ImGui::TextUnformatted(dummyText.c_str());
        ImGui::TextUnformatted(dummyText.c_str());
        ImGui::TextUnformatted(dummyText.c_str());

        for (int32_t i = 0; i < paddingSpace; ++i)
        {
            ImGui::TextUnformatted(dummyText.c_str());
        }

        for (uint32_t j = 0; j < 2; ++j)
        {
            for (uint32_t i = 0; i < pinCount; ++i)
            {
                // custom pins as an extension of the built ones
                ImVec2 inputPos = currentScreenPos;
                inputPos.y += pCurrentNode->Size.y * ((i + 1) / static_cast<float>(pinCount + 1));
                ImU32 pinColor = isInputs ? pCurrentNode->inputColors[i] : pCurrentNode->outputColors[i];

                // fill in circle for the pin if connected to a link
                if (pCurrentNode->pinIsConnected(i, isInputs))
                {
                    ImGui::GetWindowDrawList()->AddCircleFilled(ImVec2(inputPos.x, inputPos.y), kPinRadius, pinColor);
                }

                if (ImGui::IsMouseHoveringRect(
                        ImVec2(inputPos.x + pinRectBoundsOffsetx.x, inputPos.y - kPinRadius),
                        ImVec2(inputPos.x + pinRectBoundsOffsetx.y, inputPos.y + kPinRadius)
                    ))
                {
                    uint32_t hoveredPinColor = (pinColor == kGraphOutputsColor)
                                                   ? (pinColor | 0xFF000000)
                                                   : ImGui::GetColorU32(pGraphEditorGui->GetStyle().color_node_title);
                    ImGui::GetWindowDrawList()->AddCircleFilled(ImVec2(inputPos.x, inputPos.y), kPinRadius, hoveredPinColor);

                    if (pRenderPass)
                    {
                        pGraphEditorGui->setPopupNode(pCurrentNode);
                        pGraphEditorGui->setPopupPin(i, !static_cast<bool>(j));
                    }
                }
                else
                {
                    ImGui::GetWindowDrawList()->AddCircle(ImVec2(inputPos.x, inputPos.y), kPinRadius, pinColor);
                }

                auto pText = isInputs ? pCurrentNode->InputNames[i] : pCurrentNode->OutputNames[i];
                bool drawLabel = !(pText[0] == '#');

                if (pinColor == kGraphOutputsColor)
                {
                    ImVec2 arrowPoints[3] = {
                        {inputPos.x + kPinRadius * 3.0f / 2.0f, inputPos.y + kPinRadius},
                        {inputPos.x + kPinRadius * 3.0f / 2.0f + kPinRadius, inputPos.y},
                        {inputPos.x + kPinRadius * 3.0f / 2.0f, inputPos.y - kPinRadius}};
                    ImGui::GetWindowDrawList()->AddPolyline(arrowPoints, 3, pinColor, false, 3.0f);
                }
                else if (!drawLabel && pinColor == kExecutionEdgeColor)
                {
                    // we can draw anything special for execution edge inputs here
                }

                ImGui::SetCursorScreenPos(
                    {inputPos.x + pinOffsetx -
                         ((pinOffsetx < 0.0f) ? ImGui::CalcTextSize(isInputs ? pCurrentNode->InputNames[i] : pCurrentNode->OutputNames[i]).x
                                              : 0.0f),
                     inputPos.y - kPinRadius}
                );

                slotNum++;
                if (drawLabel)
                    ImGui::TextUnformatted(pText);
            }

            // reset and set up offsets for the output pins
            slotNum = 1.0f;
            currentScreenPos.x += pCurrentNode->Size.x;
            pinOffsetx *= -1.0f;
            pinRectBoundsOffsetx.y = kPinRadius;
            pinCount = static_cast<uint32_t>(pCurrentNode->OutputsCount);
            isInputs = false;
        }

        ImGui::SetCursorScreenPos(oldScreenPos);

        for (int32_t i = 0; i < paddingSpace; ++i)
        {
            ImGui::TextUnformatted(dummyText.c_str());
        }

        ImGui::TextUnformatted(dummyText.c_str());

        return false;
    }

    void initialize(
        const std::string& name,
        const std::string& outputsString,
        const std::string& inputsString,
        uint32_t guiNodeID,
        RenderPass* pRenderPass
    )
    {
        init(name.c_str(), Pos, inputsString.c_str(), outputsString.c_str(), guiNodeID);

        if (pRenderPass)
        {
            mpRenderPass = pRenderPass;
            const float4 nodeColor = Gui::pickUniqueColor(pRenderPass->getType());
            overrideTitleBgColor = ImGui::GetColorU32({nodeColor.x, nodeColor.y, nodeColor.z, nodeColor.w});
        }

        bool isInputs = true;
        uint32_t pinCount = static_cast<uint32_t>(InputsCount);
        for (uint32_t j = 0; j < 2; ++j)
        {
            for (uint32_t i = 0; i < pinCount; ++i)
            {
                if (isInputs)
                    inputColors[i] = ImColor(150, 150, 150, 150);
                else
                    outputColors[i] = ImColor(150, 150, 150, 150);
            }
            pinCount = static_cast<uint32_t>(OutputsCount);
            isInputs = false;
        }
    }

    static RenderGraphNode* create(const ImVec2& pos)
    {
        RenderGraphNode* node = (RenderGraphNode*)ImGui::MemAlloc(sizeof(RenderGraphNode));
        IM_PLACEMENT_NEW(node) RenderGraphNode();

        node->fields.addFieldCustom(static_cast<ImGui::FieldInfo::RenderFieldDelegate>(renderUI), nullptr, node);
        node->Pos = pos;
        return node;
    }

private:
};

ImGui::Node* RenderGraphUI::NodeGraphEditorGui::addAndInitNode(
    int nodeType,
    const std::string& name,
    const std::string& outputsString,
    const std::string& inputsString,
    uint32_t guiNodeID,
    RenderPass* pCurrentRenderPass,
    const ImVec2& pos
)
{
    // set init data for new node to obtain
    RenderGraphNode* newNode = static_cast<RenderGraphNode*>(addNode(nodeType, pos, nullptr));
    newNode->initialize(name, outputsString, inputsString, guiNodeID, pCurrentRenderPass);
    return newNode;
}

void RenderGraphUI::NodeGraphEditorGui::setLinkFromGui(
    ImGui::NodeLink& link,
    ImGui::NodeGraphEditor::LinkState state,
    ImGui::NodeGraphEditor& editor
)
{
    if (state == ImGui::NodeGraphEditor::LinkState::LS_ADDED)
    {
        RenderGraphNode *inputNode = static_cast<RenderGraphNode*>(link.InputNode),
                        *outputNode = static_cast<RenderGraphNode*>(link.OutputNode);
        RenderGraphUI::NodeGraphEditorGui* pGraphEditorGui = static_cast<RenderGraphUI::NodeGraphEditorGui*>(&editor);

        bool addStatus = false;
        std::string outputName = inputNode->getOutputName(link.InputSlot);
        addStatus = pGraphEditorGui->getRenderGraphUI()->addLink(
            inputNode->getName(), outputNode->getName(), outputName, outputNode->getInputName(link.OutputSlot), link.LinkColor
        );

        // immediately remove link if it is not a legal edge in the render graph
        if (!addStatus && !editor.isInited()) //  only call after graph is setup
        {
            // does not call link callback surprisingly enough
            editor.removeLink(link.InputNode, link.InputSlot, link.OutputNode, link.OutputSlot);
            return;
        }

        if (outputName[0] == '#')
        {
            static_cast<RenderGraphNode*>(link.InputNode)->setPinColor(link.LinkColor, link.InputSlot, false);
            static_cast<RenderGraphNode*>(link.OutputNode)->setPinColor(link.LinkColor, link.OutputSlot, true);
        }
    }
}

// callback for ImGui setting link from render graph changes
void RenderGraphUI::NodeGraphEditorGui::setLinkFromGraph(
    ImGui::NodeLink& link,
    ImGui::NodeGraphEditor::LinkState state,
    ImGui::NodeGraphEditor& editor
)
{
    if (state == ImGui::NodeGraphEditor::LinkState::LS_ADDED)
    {
        bool addStatus = false;

        // immediately remove link if it is not a legal edge in the render graph
        if (!addStatus && !editor.isInited()) //  only call after graph is setup
        {
            // does not call link callback surprisingly enough
            editor.removeLink(link.InputNode, link.InputSlot, link.OutputNode, link.OutputSlot);
            return;
        }
    }
}

ImGui::Node* RenderGraphUI::NodeGraphEditorGui::createNode(int, const ImVec2& pos, const ImGui::NodeGraphEditor&)
{
    return RenderGraphUI::RenderGraphNode::create(pos);
}

void RenderGraphUI::addRenderPass(const std::string& name, const std::string& nodeTypeName)
{
    mpIr->createPass(nodeTypeName, name);
    mShouldUpdate = true;
}

void RenderGraphUI::addOutput(const std::string& outputParam)
{
    size_t offset = outputParam.find('.');
    std::string outputPass = outputParam.substr(0, offset);
    std::string outputField = outputParam.substr(offset + 1, outputParam.size());

    const auto passUIIt = mRenderPassUI.find(outputPass);
    if (passUIIt == mRenderPassUI.end())
    {
        msgBox("Error", "Error setting graph output. Can't find node name.");
        return;
    }
    auto& passUI = passUIIt->second;
    const auto outputIt = passUI.mNameToIndexOutput.find(outputField);
    if (outputIt == passUI.mNameToIndexOutput.end())
    {
        msgBox("Error", "Error setting graph output. Can't find output name.");
        return;
    }
    passUI.mOutputPins[outputIt->second].mIsGraphOutput = true;
    mpIr->markOutput(outputParam);
    mRebuildDisplayData = true;
    mShouldUpdate = true;
}

void RenderGraphUI::addOutput(const std::string& outputPass, const std::string& outputField)
{
    std::string outputParam = outputPass + "." + outputField;
    addOutput(outputParam);
}

void RenderGraphUI::removeOutput(const std::string& outputPass, const std::string& outputField)
{
    std::string outputParam = outputPass + "." + outputField;
    mpIr->unmarkOutput(outputParam);
    auto& passUI = mRenderPassUI[outputPass];
    passUI.mOutputPins[passUI.mNameToIndexOutput[outputField]].mIsGraphOutput = false;
    mShouldUpdate = true;
}

bool RenderGraphUI::autoResolveWarning(const std::string& srcString, const std::string& dstString)
{
    std::string warningMsg = std::string("Warning: Edge ") + srcString + " - " + dstString + " can auto-resolve.\n";
    MsgBoxButton button = msgBox("Warning", warningMsg, MsgBoxType::OkCancel);

    if (button == MsgBoxButton::Ok)
    {
        mLogString += warningMsg;
        return true;
    }

    return false;
}

bool RenderGraphUI::addLink(
    const std::string& srcPass,
    const std::string& dstPass,
    const std::string& srcField,
    const std::string& dstField,
    uint32_t& color
)
{
    // outputs warning if edge could not be created
    std::string srcString = srcPass + (srcField[0] == '#' ? "" : ".") + srcField;
    std::string dstString = dstPass + (dstField[0] == '#' ? "" : ".") + dstField;
    bool canCreateEdge = (mpRenderGraph->getEdge(srcString, dstString) == ((uint32_t)-1));
    if (!canCreateEdge)
        return canCreateEdge;

    // update the ui to reflect the connections. This data is used for removal
    RenderPassUI& srcRenderPassUI = mRenderPassUI[srcPass];
    RenderPassUI& dstRenderPassUI = mRenderPassUI[dstPass];
    const auto outputIt = srcRenderPassUI.mNameToIndexOutput.find(srcField);
    const auto inputIt = dstRenderPassUI.mNameToIndexInput.find(dstField);
    // if one filed is empty, check that the other is as well
    if ((dstField[0] == '#') || (srcField[0] == '#'))
        canCreateEdge &= (srcField[0] == '#') && (dstField[0] == '#');
    // check that link could exist
    canCreateEdge &= (outputIt != srcRenderPassUI.mNameToIndexOutput.end()) && (inputIt != dstRenderPassUI.mNameToIndexInput.end());
    // check that the input is not already connected
    canCreateEdge &= (mInputPinStringToLinkID.find(dstString) == mInputPinStringToLinkID.end());

    if (canCreateEdge)
    {
        uint32_t srcPinIndex = outputIt->second;
        uint32_t dstPinIndex = inputIt->second;
        srcRenderPassUI.mOutputPins[srcPinIndex].mConnectedPinName = dstField;
        srcRenderPassUI.mOutputPins[srcPinIndex].mConnectedNodeName = dstPass;
        dstRenderPassUI.mInputPins[dstPinIndex].mConnectedPinName = srcField;
        dstRenderPassUI.mInputPins[dstPinIndex].mConnectedNodeName = srcPass;

        if (!(dstField[0] == '#'))
        {
            color = kEdgesColor;

            // TODO: Auto-resolve is not fully working and will be removed (see #1275).
            // RenderPassReflection srcReflection = mpRenderGraph->mNodeData[mpRenderGraph->getPassIndex(srcPass)].pPass->reflect({});
            // RenderPassReflection dstReflection = mpRenderGraph->mNodeData[mpRenderGraph->getPassIndex(dstPass)].pPass->reflect({});
            //
            // bool canAutoResolve = mpRenderGraph->canAutoResolve(srcReflection.getField(srcField), dstReflection.getField(dstField));
            // if (canAutoResolve && mDisplayAutoResolvePopup) canCreateEdge = autoResolveWarning(srcString, dstString);
            // color = canAutoResolve ? kAutoResolveEdgesColor : kEdgesColor;
        }

        if (canCreateEdge)
        {
            mShouldUpdate = true;

            if (dstField[0] == '#')
            {
                // rebuilds data to avoid repeated code
                mRebuildDisplayData = true;
                color = kExecutionEdgeColor;
                mpIr->addEdge(srcPass, dstPass);
            }
            else
            {
                mpIr->addEdge(srcString, dstString);
            }
        }
    }
    else
    {
        mLogString += "Unable to create edge for graph. \n";
    }

    return canCreateEdge;
}

void RenderGraphUI::removeEdge(
    const std::string& srcPass,
    const std::string& dstPass,
    const std::string& srcField,
    const std::string& dstField
)
{
    if (dstField[0] == '#')
    {
        FALCOR_ASSERT(srcField[0] == '#');
        mpIr->removeEdge(srcPass, dstPass);
    }
    else
    {
        mpIr->removeEdge(srcPass + "." + srcField, dstPass + "." + dstField);
    }
    mShouldUpdate = true;
}

void RenderGraphUI::removeRenderPass(const std::string& name)
{
    mRebuildDisplayData = true;
    mpIr->removePass(name);
    mShouldUpdate = true;
}

void RenderGraphUI::updateGraph(RenderContext* pRenderContext)
{
    if (!mShouldUpdate)
        return;
    std::string newCommands = mpIr->getIR();
    mpIr = std::make_shared<RenderGraphIR>(mRenderGraphName, false); // reset
    if (mLastCommand == newCommands)
        return;

    mLastCommand = newCommands;
    if (mRecordUpdates)
        mUpdateCommands += newCommands;

    // update reference graph to check if valid before sending to next
    // TODO: Rendergraph scripts should be executed in an isolated scripting context.
    Scripting::getDefaultContext().setObject("g", mpRenderGraph);
    Scripting::runScript(newCommands);
    if (newCommands.size())
        mLogString += newCommands;

    // only send updates that we know are valid.
    if (mpRenderGraph->compile(pRenderContext) == false)
        mLogString += "Graph is currently invalid\n";
    mShouldUpdate = false;
    mRebuildDisplayData = true;
}

void RenderGraphUI::writeUpdateScriptToFile(RenderContext* pRenderContext, const std::filesystem::path& filePath, float lastFrameTime)
{
    if ((mTimeSinceLastUpdate += lastFrameTime) < kUpdateTimeInterval)
        return;
    mTimeSinceLastUpdate = 0.0f;
    if (!mUpdateCommands.size())
        return;

    // only send delta of updates once the graph is valid
    if (mpRenderGraph->compile(pRenderContext) == false)
        return;
    std::ofstream outputFileStream(filePath, std::ios_base::out);
    outputFileStream << mUpdateCommands;
    mUpdateCommands.clear();
}

RenderGraphUI::RenderGraphUI() : mNewNodeStartPosition(-40.0f, 100.0f)
{
    mNextPassName.resize(255, 0);
}

RenderGraphUI::RenderGraphUI(const ref<RenderGraph>& pGraph, const std::string& graphName)
    : mpRenderGraph(pGraph), mNewNodeStartPosition(-40.0f, 100.0f), mRenderGraphName(graphName)
{
    mNextPassName.resize(255, 0);
    mpNodeGraphEditor = std::make_shared<NodeGraphEditorGui>(this);
    mpIr = std::make_shared<RenderGraphIR>(graphName, false);
}

RenderGraphUI::~RenderGraphUI() {}

void RenderGraphUI::NodeGraphEditorGui::setNode(ImGui::Node*& node, ImGui::NodeGraphEditor::NodeState state, ImGui::NodeGraphEditor& editor)
{
    RenderGraphNode* pRenderGraphNode = static_cast<RenderGraphNode*>(node);
    RenderGraphUI::NodeGraphEditorGui* pGraphEditor = static_cast<RenderGraphUI::NodeGraphEditorGui*>(&editor);
    if (!editor.isInited())
    {
        if (state == ImGui::NodeGraphEditor::NodeState::NS_DELETED)
        {
            pRenderGraphNode->getFields().clear();
            pGraphEditor->getRenderGraphUI()->removeRenderPass(node->getName());
        }
    }
    if (state == ImGui::NodeGraphEditor::NodeState::NS_ADDED)
    {
        pGraphEditor->getRenderGraphUI()->addRenderPass(node->getName(), pRenderGraphNode->mpRenderPass->getType());
    }
}

void RenderPassUI::addUIPin(
    const std::string& fieldName,
    uint32_t guiPinID,
    bool isInput,
    const std::string& connectedPinName,
    const std::string& connectedNodeName,
    bool isGraphOutput
)
{
    auto& pinsRef = isInput ? mInputPins : mOutputPins;
    auto& nameToIndexMapRef = isInput ? mNameToIndexInput : mNameToIndexOutput;

    if (pinsRef.size() <= guiPinID)
    {
        pinsRef.resize(guiPinID + 1);
    }

    PinUI& pinUIData = pinsRef[guiPinID];
    pinUIData.mPinName = fieldName;
    pinUIData.mGuiPinID = guiPinID;
    pinUIData.mConnectedPinName = connectedPinName;
    pinUIData.mConnectedNodeName = connectedNodeName;
    pinUIData.mIsGraphOutput = isGraphOutput;

    nameToIndexMapRef.insert(std::make_pair(pinUIData.mPinName, static_cast<uint32_t>(guiPinID)));
}

void RenderPassUI::renderPinUI(const std::string& passName, RenderGraphUI* pGraphUI, uint32_t index, bool input)
{
    RenderPassUI::PinUI& pinUI = input ? mInputPins[index] : mOutputPins[index];

    size_t fieldIndex = -1;
    for (size_t i = 0; i < mReflection.getFieldCount(); ++i)
    {
        if (mReflection.getField(i)->getName() == pinUI.mPinName)
        {
            fieldIndex = i;
            break;
        }
    }

    if (fieldIndex != -1)
    {
        // only render ui if is input or output
        const RenderPassReflection::Field& field = *mReflection.getField(fieldIndex);
        if (is_set(field.getVisibility(), RenderPassReflection::Field::Visibility::Input) ||
            is_set(field.getVisibility(), RenderPassReflection::Field::Visibility::Output))
        {
            pinUI.renderUI(field, pGraphUI, passName);
        }
    }
}

void RenderPassUI::PinUI::renderFieldInfo(
    const RenderPassReflection::Field& field,
    RenderGraphUI* pGraphUI,
    const std::string& passName,
    const std::string& fieldName
)
{
    RenderPassReflection::Field::Visibility type = field.getVisibility();
    uint32_t isInput = is_set(type, RenderPassReflection::Field::Visibility::Input);
    uint32_t isOutput = is_set(type, RenderPassReflection::Field::Visibility::Output);
    bool isExecutionPin = fieldName[0] == '#';

    if (isExecutionPin)
    {
        ImGui::TextUnformatted("Execution Pin");
        return;
    }

    ImGui::SameLine();
    ImGui::TextUnformatted((std::string("Field Name: ") + fieldName).c_str());
    ImGui::Separator();
    ImGui::TextUnformatted((field.getDesc() + "\n\n").c_str());

    ImGui::TextUnformatted("ResourceFlags : ");

    if (isInput && isOutput)
    {
        ImGui::SameLine();
        ImGui::TextUnformatted("InputOutput");
    }
    else if (isInput)
    {
        ImGui::SameLine();
        ImGui::TextUnformatted("Input");
    }
    else if (isOutput)
    {
        ImGui::SameLine();
        ImGui::TextUnformatted("Output");
    }

    ImGui::TextUnformatted("ResourceType : ");
    ImGui::SameLine();
    ImGui::TextUnformatted(to_string(field.getType()).c_str());

    ImGui::TextUnformatted("Width: ");
    ImGui::SameLine();
    ImGui::TextUnformatted(std::to_string(field.getWidth()).c_str());

    ImGui::TextUnformatted("Height: ");
    ImGui::SameLine();
    ImGui::TextUnformatted(std::to_string(field.getHeight()).c_str());

    ImGui::TextUnformatted("Depth: ");
    ImGui::SameLine();
    ImGui::TextUnformatted(std::to_string(field.getDepth()).c_str());

    ImGui::TextUnformatted("Sample Count: ");
    ImGui::SameLine();
    ImGui::TextUnformatted(std::to_string(field.getSampleCount()).c_str());

    ImGui::TextUnformatted("ResourceFormat: ");
    ImGui::SameLine();
    ImGui::TextUnformatted(to_string(field.getFormat()).c_str());

    ImGui::TextUnformatted("BindFlags: ");
    ImGui::SameLine();
    ImGui::TextUnformatted(to_string(field.getBindFlags()).c_str());

    ImGui::TextUnformatted("Flags: ");
    switch (field.getFlags())
    {
    case RenderPassReflection::Field::Flags::None:
        ImGui::SameLine();
        ImGui::TextUnformatted("None");
        break;
    case RenderPassReflection::Field::Flags::Optional:
        ImGui::SameLine();
        ImGui::TextUnformatted("Optional");
        break;
    case RenderPassReflection::Field::Flags::Persistent:
        ImGui::SameLine();
        ImGui::TextUnformatted("Persistent");
        break;
    default:
        FALCOR_UNREACHABLE();
    }
}

void RenderPassUI::PinUI::renderUI(const RenderPassReflection::Field& field, RenderGraphUI* pGraphUI, const std::string& passName)
{
    ImGui::TextUnformatted(mIsGraphOutput ? "Graph Output : " : "");
    renderFieldInfo(field, pGraphUI, passName, mPinName);
    bool isExecutionPin = mPinName[0] == '#';

    if (!isExecutionPin && is_set(field.getVisibility(), RenderPassReflection::Field::Visibility::Output))
    {
        bool isGraphOut = mIsGraphOutput;
        if (ImGui::Checkbox("Graph Output", &mIsGraphOutput))
        {
            if (isGraphOut && !mIsGraphOutput)
            {
                pGraphUI->removeOutput(passName, mPinName);
            }
            else if (!isGraphOut && mIsGraphOutput)
            {
                pGraphUI->addOutput(passName, mPinName);
            }
        }
    }

    ImGui::Separator();
}

void RenderGraphUI::renderPopupMenu()
{
    bool isPopupOpen = ImGui::IsPopupOpen("PinMenu");
    if (!isPopupOpen)
    {
        ImGui::OpenPopup("PinMenu");
    }

    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8));
    if (ImGui::BeginPopup("PinMenu"))
    {
        if (!isPopupOpen)
            ImGui::SetWindowPos({ImGui::GetWindowPos().x - 8, ImGui::GetWindowPos().y - 8});
        else if (isPopupOpen && !ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_ChildWindows))
        {
            if (ImGui::IsMouseClicked(0) || ImGui::IsMouseClicked(1))
            {
                mpNodeGraphEditor->selectedLink = -1;
                mpNodeGraphEditor->setPopupNode(nullptr);
                mpNodeGraphEditor->setPopupPin(-1, false);
            }
            else
                mpNodeGraphEditor->deselectPopupPin();
        }

        if (mpNodeGraphEditor->getPopupNode() && mpNodeGraphEditor->getPopupPinIndex() != -1)
        {
            const std::string& passName = mpNodeGraphEditor->getPopupNode()->getName();
            RenderPassUI& renderPassUI = mRenderPassUI[passName];
            renderPassUI.renderPinUI(passName, this, mpNodeGraphEditor->getPopupPinIndex(), mpNodeGraphEditor->isPopupPinInput());
            ImGui::Separator();
        }

        if (mpNodeGraphEditor->selectedLink != -1)
        {
            ImGui::NodeLink& selectedLink = mpNodeGraphEditor->getLink(mpNodeGraphEditor->selectedLink);
            std::string srcPassName = std::string(selectedLink.InputNode->getName());
            std::string dstPassName = std::string(selectedLink.OutputNode->getName());
            ImGui::TextUnformatted((std::string("Edge: ") + srcPassName + '-' + dstPassName).c_str());
            std::string inputName =
                std::string(static_cast<RenderGraphNode*>(selectedLink.OutputNode)->getInputName(selectedLink.OutputSlot));
            std::string inputString = dstPassName + (inputName.empty() ? "" : ".") + inputName;
            uint32_t linkID = mInputPinStringToLinkID[inputString];
            auto edgeIt = mpRenderGraph->mEdgeData.find(linkID);

            // link exists, but is not an edge (such as graph output edge)
            if (edgeIt != mpRenderGraph->mEdgeData.end())
            {
                RenderGraph::EdgeData& edgeData = edgeIt->second;

                if (edgeData.srcField.size())
                {
                    ImGui::TextUnformatted("Src Field : ");
                    ImGui::SameLine();
                    ImGui::TextUnformatted(edgeData.srcField.c_str());
                }

                if (edgeData.dstField.size())
                {
                    ImGui::TextUnformatted("Dst Field : ");
                    ImGui::SameLine();
                    ImGui::TextUnformatted(edgeData.dstField.c_str());
                }
            }
        }

        ImGui::EndPopup();
    }
    ImGui::PopStyleVar();
}

void RenderGraphUI::renderUI(RenderContext* pRenderContext, Gui* pGui)
{
    static std::string dragAndDropText;
    ImGui::GetIO().FontAllowUserScaling = true; // FIXME
    mpNodeGraphEditor->mpRenderGraphUI = this;
    mpNodeGraphEditor->setCurrentGui(pGui);
    mpNodeGraphEditor->show_top_pane = false;
    mpNodeGraphEditor->show_node_copy_paste_buttons = false;
    mpNodeGraphEditor->show_connection_names = false;
    mpNodeGraphEditor->show_left_pane = false;
    mpNodeGraphEditor->setLinkCallback(NodeGraphEditorGui::setLinkFromGui);
    mpNodeGraphEditor->setNodeCallback(NodeGraphEditorGui::setNode);

    ImVec2 mousePos = ImGui::GetMousePos();
    ImGui::NodeGraphEditor::Style& style = mpNodeGraphEditor->GetStyle();
    style.color_node_frame_selected = ImGui::ColorConvertFloat4ToU32({226.0f / 255.0f, 190.0f / 255.0f, 42.0f / 255.0f, 0.8f});
    style.color_node_frame_active = style.color_node_frame_selected;
    style.node_slots_radius = kPinRadius;

    // update the deleted links from the GUI since the library doesn't call its own callback
    if (mRebuildDisplayData)
    {
        mRebuildDisplayData = false;
        mpNodeGraphEditor->setNodeCallback(nullptr);
        mpNodeGraphEditor->reset();
    }
    else
    {
        updatePins(false);
    }

    ImVector<const ImGui::Node*> selectedNodes;
    mpNodeGraphEditor->getSelectedNodes(selectedNodes);

    // push update commands for the open pop-up
    Gui::Window renderWindow(pGui, "Render UI", {250, 200});
    for (uint32_t i = 0; i < static_cast<uint32_t>(selectedNodes.size()); ++i)
    {
        std::string renderUIName = selectedNodes.Data[i]->getName();

        if (auto renderGroup = renderWindow.group(renderUIName, true))
        {
            auto pPass = mpRenderGraph->getPass(renderUIName);
            bool internalResources = false;

            renderGroup.separator();
            ImGui::TextWrapped("Description: %s", pPass->getDesc().c_str());
            renderGroup.separator();

            pPass->renderUI(renderGroup);

            for (uint32_t j = 0; j < mRenderPassUI[renderUIName].mReflection.getFieldCount(); ++j)
            {
                const auto& field = *mRenderPassUI[renderUIName].mReflection.getField(j);
                if (is_set(field.getVisibility(), RenderPassReflection::Field::Visibility::Internal))
                {
                    if (!internalResources)
                    {
                        renderGroup.separator();
                        renderGroup.text("\n\n");
                        renderGroup.separator();
                        renderGroup.text("Internal Resources:");
                        renderGroup.text("\n\n");
                        renderGroup.separator();
                    }
                    RenderPassUI::PinUI::renderFieldInfo(field, this, renderUIName, field.getName());
                    internalResources = true;
                }
            }
            if (internalResources)
                renderGroup.separator();
            // TODO -- only call this with data change
            if (ImGui::IsWindowFocused())
            {
                mpIr->updatePass(renderUIName, pPass->getProperties());
            }
            mShouldUpdate = true;
        }
    }

    renderWindow.release();

    if (mpNodeGraphEditor->getPopupPinIndex() != uint32_t(-1) || (mpNodeGraphEditor->selectedLink != -1))
    {
        renderPopupMenu();
    }
    else
    {
        if (ImGui::IsPopupOpen("PinMenu"))
        {
            ImGui::CloseCurrentPopup();
        }
    }

    if (!mpNodeGraphEditor->isInited())
    {
        mpNodeGraphEditor->render();

        std::string statement;
        bool addPass = false;
        bool b = false;
        if (ImGui::BeginDragDropTarget())
        {
            auto dragDropPayload = ImGui::AcceptDragDropPayload("RenderPassType");
            b = dragDropPayload && dragDropPayload->IsDataType("RenderPassType") && (dragDropPayload->Data != nullptr);
            if (b)
            {
                statement.resize(dragDropPayload->DataSize);
                std::memcpy(&statement.front(), dragDropPayload->Data, dragDropPayload->DataSize);
            }

            ImGui::EndDragDropTarget();
        }

        if (b)
        {
            dragAndDropText = statement;
            mNewNodeStartPosition = {-mpNodeGraphEditor->offset.x + mousePos.x, -mpNodeGraphEditor->offset.y + mousePos.y};
            mNewNodeStartPosition /= ImGui::GetCurrentWindow()->FontWindowScale;
            mNextPassName = statement;
            // only open pop-up if right clicked
            mDisplayDragAndDropPopup = ImGui::GetIO().KeyCtrl;
            addPass = !mDisplayDragAndDropPopup;
        }

        if (mDisplayDragAndDropPopup)
        {
            Gui::Window createGraphWindow(
                pGui, "CreateNewGraph", mDisplayDragAndDropPopup, {256, 128}, {(uint32_t)mousePos.x, (uint32_t)mousePos.y}
            );

            createGraphWindow.textbox("Pass Name", mNextPassName);
            if (createGraphWindow.button("create##renderpass"))
            {
                addPass = true;
            }
            if (createGraphWindow.button("cancel##renderPass"))
            {
                mDisplayDragAndDropPopup = false;
            }

            createGraphWindow.release();
        }

        if (addPass)
        {
            // Get unique name by adding incrementing number suffix if necessary.
            const std::string passName = mNextPassName;
            uint32_t passIndex = 0;
            while (mpRenderGraph->doesPassExist(mNextPassName))
            {
                mNextPassName = passName + std::to_string(passIndex++);
            }

            mpIr->createPass(dragAndDropText, mNextPassName);
            mAddedFromDragAndDrop = true;
            mDisplayDragAndDropPopup = false;
            mShouldUpdate = true;
            if (mMaxNodePositionX < mNewNodeStartPosition.x)
                mMaxNodePositionX = mNewNodeStartPosition.x;
        }

        //  set editor window behind other windows. set top menu bar to always be infront.this is repeated for other render call
        ImGui::BringWindowToDisplayBack(ImGui::FindWindowByName("Graph Editor"));
        ImGui::BringWindowToDisplayFront(ImGui::FindWindowByName("##MainMenuBar"));
        return;
    }

    updateDisplayData(pRenderContext);

    mAllNodeTypes.clear();

    // reset internal data of node if all nodes deleted
    if (!mRenderPassUI.size())
    {
        mpNodeGraphEditor->clear();
        mpNodeGraphEditor->render();
        return;
    }

    for (auto& nodeTypeString : mAllNodeTypeStrings)
    {
        mAllNodeTypes.push_back(nodeTypeString.c_str());
    }

    mpNodeGraphEditor->registerNodeTypes(
        mAllNodeTypes.data(), static_cast<uint32_t>(mAllNodeTypes.size()), NodeGraphEditorGui::createNode, 0, -1, 0, 0
    );

    for (auto& currentPass : mRenderPassUI)
    {
        auto& currentPassUI = currentPass.second;
        std::string inputsString;
        std::string outputsString;
        std::string nameString;

        for (const auto& currentPinUI : currentPassUI.mInputPins)
        {
            // Connect the graph nodes for each of the edges
            // need to iterate in here in order to use the right indices
            const std::string& currentPinName = currentPinUI.mPinName;
            inputsString += inputsString.size() ? (";" + currentPinName) : currentPinName;
        }

        for (const auto& currentPinUI : currentPassUI.mOutputPins)
        {
            const std::string& currentPinName = currentPinUI.mPinName;
            outputsString += outputsString.size() ? (";" + currentPinName) : currentPinName;
        }

        uint32_t guiNodeID = currentPassUI.mGuiNodeID;
        RenderPass* pNodeRenderPass = mpRenderGraph->getPass(currentPass.first).get();
        nameString = currentPass.first;

        if (!mpNodeGraphEditor->getAllNodesOfType(currentPassUI.mGuiNodeID, nullptr, false))
        {
            float2 nextPosition =
                mAddedFromDragAndDrop ? mNewNodeStartPosition : getNextNodePosition(mpRenderGraph->getPassIndex(nameString));

            mpNodeGraphEditor->getNodeFromID(guiNodeID) = mpNodeGraphEditor->addAndInitNode(
                guiNodeID, nameString, outputsString, inputsString, guiNodeID, pNodeRenderPass, ImVec2(nextPosition.x, nextPosition.y)
            );
            mAddedFromDragAndDrop = false;
        }
    }

    updatePins();
    mpNodeGraphEditor->render();
    ImGui::BringWindowToDisplayBack(ImGui::FindWindowByName("Graph Editor"));
    ImGui::BringWindowToDisplayFront(ImGui::FindWindowByName("##MainMenuBar"));
}

void RenderGraphUI::reset()
{
    mpNodeGraphEditor->reset();
    mpNodeGraphEditor->clear();
    mRebuildDisplayData = true;
}

void RenderGraphUI::setRecordUpdates(bool recordUpdates)
{
    mRecordUpdates = recordUpdates;
}

void RenderGraphUI::updatePins(bool addLinks)
{
    //  Draw pin connections. All the nodes have to be added to the GUI before the connections can be drawn
    for (auto& currentPass : mRenderPassUI)
    {
        auto& currentPassUI = currentPass.second;

        for (auto& currentPinUI : currentPassUI.mOutputPins)
        {
            const std::string& currentPinName = currentPinUI.mPinName;

            if (addLinks)
            {
                const auto& inputPins = mOutputToInputPins.find(currentPass.first + "." + currentPinName);
                if (inputPins != mOutputToInputPins.end())
                {
                    for (const auto& connectedPin : (inputPins->second))
                    {
                        if (!mpNodeGraphEditor->isLinkPresent(
                                mpNodeGraphEditor->getNodeFromID(currentPassUI.mGuiNodeID),
                                currentPinUI.mGuiPinID,
                                mpNodeGraphEditor->getNodeFromID(connectedPin.second),
                                connectedPin.first
                            ))
                        {
                            RenderGraphNode* pNode = static_cast<RenderGraphNode*>(mpNodeGraphEditor->getNodeFromID(connectedPin.second));
                            std::string dstName = pNode->getInputName(connectedPin.first);
                            uint32_t edgeColor = kEdgesColor;
                            // execution edges
                            if (dstName[0] == '#')
                            {
                                edgeColor = kExecutionEdgeColor;
                            }
                            else
                            {
                                std::string srcString = currentPass.first + "." + currentPinName;
                                std::string dstString = std::string(pNode->getName()) + "." + dstName;
                                const RenderPassReflection::Field& srcPin = *currentPassUI.mReflection.getField(currentPinName);
                                const RenderPassReflection::Field& dstPin = *mRenderPassUI[pNode->getName()].mReflection.getField(dstName);

                                if (false /*mpRenderGraph->canAutoResolve(srcPin, dstPin)*/)
                                {
                                    mLogString += std::string("Warning: Edge ") + srcString + " - " + dstName + " can auto-resolve.\n";
                                    edgeColor = kAutoResolveEdgesColor;
                                }
                            }

                            mpNodeGraphEditor->addLinkFromGraph(
                                mpNodeGraphEditor->getNodeFromID(currentPassUI.mGuiNodeID),
                                currentPinUI.mGuiPinID,
                                mpNodeGraphEditor->getNodeFromID(connectedPin.second),
                                connectedPin.first,
                                false,
                                edgeColor
                            );

                            RenderGraphNode* pDstGraphNode =
                                static_cast<RenderGraphNode*>(mpNodeGraphEditor->getNodeFromID(connectedPin.second));
                            RenderGraphNode* pSrcGraphNode =
                                static_cast<RenderGraphNode*>(mpNodeGraphEditor->getNodeFromID(currentPassUI.mGuiNodeID));
                            pDstGraphNode->mInputPinConnected[connectedPin.first] = true;
                            pSrcGraphNode->mOutputPinConnected[currentPinUI.mGuiPinID] = true;
                            if (dstName[0] == '#')
                            {
                                pSrcGraphNode->setPinColor(kExecutionEdgeColor, currentPinUI.mGuiPinID, false);
                                pDstGraphNode->setPinColor(kExecutionEdgeColor, connectedPin.first, true);
                            }
                        }
                    }
                }

                // mark graph outputs to graph output node
                if (currentPinUI.mIsGraphOutput)
                {
                    static_cast<RenderGraphNode*>(mpNodeGraphEditor->getNodeFromID(currentPassUI.mGuiNodeID))
                        ->setPinColor(kGraphOutputsColor, currentPinUI.mGuiPinID);
                }
            }
            else
            {
                if (!currentPinUI.mIsGraphOutput)
                {
                    static_cast<RenderGraphNode*>(mpNodeGraphEditor->getNodeFromID(currentPassUI.mGuiNodeID))
                        ->setPinColor(ImColor(150, 150, 150, 150), currentPinUI.mGuiPinID);
                }
            }
        }

        if (!addLinks)
        {
            for (auto& currentPinUI : currentPassUI.mInputPins)
            {
                const std::string& currentPinName = currentPinUI.mPinName;
                if (!currentPinUI.mConnectedNodeName.size())
                    continue;

                std::pair<uint32_t, uint32_t> inputIDs{currentPinUI.mGuiPinID, currentPassUI.mGuiNodeID};
                const auto& connectedNodeUI = mRenderPassUI[currentPinUI.mConnectedNodeName];
                uint32_t inputPinID = connectedNodeUI.mNameToIndexOutput.find(currentPinUI.mConnectedPinName)->second;

                if (!mpNodeGraphEditor->isLinkPresent(
                        mpNodeGraphEditor->getNodeFromID(connectedNodeUI.mGuiNodeID),
                        inputPinID,
                        mpNodeGraphEditor->getNodeFromID(inputIDs.second),
                        inputIDs.first
                    ))
                {
                    auto edgeIt = mInputPinStringToLinkID.find(currentPass.first + "." + currentPinName);
                    FALCOR_ASSERT(edgeIt != mInputPinStringToLinkID.end());
                    uint32_t edgeID = edgeIt->second;

                    removeEdge(
                        mpNodeGraphEditor->getNodeFromID(connectedNodeUI.mGuiNodeID)->getName(),
                        mpNodeGraphEditor->getNodeFromID(inputIDs.second)->getName(),
                        mpRenderGraph->mEdgeData[edgeID].srcField,
                        mpRenderGraph->mEdgeData[edgeID].dstField
                    );
                    mpRenderGraph->removeEdge(edgeID);

                    currentPinUI.mConnectedNodeName = "";
                    RenderGraphNode* pDstGraphNode = static_cast<RenderGraphNode*>(mpNodeGraphEditor->getNodeFromID(inputIDs.second));
                    RenderGraphNode* pSrcGraphNode =
                        static_cast<RenderGraphNode*>(mpNodeGraphEditor->getNodeFromID(connectedNodeUI.mGuiNodeID));

                    pDstGraphNode->mInputPinConnected[inputIDs.first] = false;
                    pSrcGraphNode->mOutputPinConnected[inputPinID] = false;
                    pSrcGraphNode->setPinColor(kPinColor, inputPinID, false);
                    pDstGraphNode->setPinColor(kPinColor, inputIDs.first, true);

                    continue;
                }
            }
        }
    }
}

float2 RenderGraphUI::getNextNodePosition(uint32_t nodeID)
{
    const float offsetX = 384.0f;
    const float offsetY = 128.0f;
    float2 newNodePosition = mNewNodeStartPosition;

    auto topologicalSort = DirectedGraphTopologicalSort::sort(*mpRenderGraph->mpGraph);

    // For each object in the vector, if it's being used in the execution, put it in the list
    for (auto& node : topologicalSort)
    {
        newNodePosition.x += offsetX;

        if (node == nodeID)
        {
            const DirectedGraph::Node* pNode = mpRenderGraph->mpGraph->getNode(nodeID);
            if (!pNode->getIncomingEdgeCount())
            {
                newNodePosition.y += offsetY * pNode->getIncomingEdgeCount() * nodeID;
                break;
            }

            for (uint32_t i = 0; i < pNode->getIncomingEdgeCount(); ++i)
            {
                uint32_t outgoingEdgeCount =
                    mpRenderGraph->mpGraph->getNode(mpRenderGraph->mpGraph->getEdge(pNode->getIncomingEdge(i))->getSourceNode())
                        ->getOutgoingEdgeCount();
                if (outgoingEdgeCount > pNode->getIncomingEdgeCount())
                {
                    // move down by index in
                    newNodePosition.y += offsetY * (outgoingEdgeCount - pNode->getIncomingEdgeCount());
                    break;
                }
            }
            break;
        }
    }

    if (newNodePosition.x > mMaxNodePositionX)
        mMaxNodePositionX = newNodePosition.x;
    return newNodePosition;
}

void RenderGraphUI::updateDisplayData(RenderContext* pRenderContext)
{
    uint32_t nodeIndex = 0;

    mOutputToInputPins.clear();

    // set of field names that have a connection and are represented in the graph
    std::unordered_set<std::string> nodeConnectedInput;
    std::unordered_set<std::string> nodeConnectedOutput;
    std::unordered_map<std::string, uint32_t> previousGuiNodeIDs;
    std::unordered_set<uint32_t> existingIDs;

    for (const auto& currentRenderPassUI : mRenderPassUI)
    {
        existingIDs.insert(currentRenderPassUI.second.mGuiNodeID);
        previousGuiNodeIDs.insert(std::make_pair(currentRenderPassUI.first, currentRenderPassUI.second.mGuiNodeID));
    }

    mpRenderGraph->compile(pRenderContext);
    mRenderPassUI.clear();
    mInputPinStringToLinkID.clear();

    // build information for displaying graph
    for (const auto& nameToIndex : mpRenderGraph->mNameToIndex)
    {
        auto pCurrentPass = mpRenderGraph->mpGraph->getNode(nameToIndex.second);
        RenderPassUI renderPassUI;
        bool addedExecutionInput = false;
        bool addedExecutionOutput = false;

        mAllNodeTypeStrings.insert(nameToIndex.first);

        while (existingIDs.find(nodeIndex) != existingIDs.end())
        {
            nodeIndex++;
        }

        // keep the GUI id from the previous frame
        auto pPreviousID = previousGuiNodeIDs.find(nameToIndex.first);
        if (pPreviousID != previousGuiNodeIDs.end())
        {
            renderPassUI.mGuiNodeID = pPreviousID->second;
        }
        else
        {
            renderPassUI.mGuiNodeID = nodeIndex;
            nodeIndex++;
        }

        // clear and rebuild reflection for each pass.
        // TODO: This is unsafe because render pass reflection may depend on the compile data, which isn't available here.
        renderPassUI.mReflection = mpRenderGraph->mNodeData[nameToIndex.second].pPass->reflect({});

        // test to see if we have hit a graph output
        std::unordered_set<std::string> passGraphOutputs;

        for (const auto& output : mpRenderGraph->mOutputs)
        {
            if (output.nodeId == nameToIndex.second)
            {
                passGraphOutputs.insert(output.field);
            }
        }

        uint32_t inputPinIndex = 0;
        uint32_t outputPinIndex = 0;

        // add all of the incoming connections
        for (uint32_t i = 0; i < pCurrentPass->getIncomingEdgeCount(); ++i)
        {
            uint32_t edgeID = pCurrentPass->getIncomingEdge(i);
            auto currentEdge = mpRenderGraph->mEdgeData[edgeID];
            uint32_t pinIndex = 0;
            inputPinIndex = 0;

            while (pinIndex < renderPassUI.mReflection.getFieldCount())
            {
                bool isInput =
                    is_set(renderPassUI.mReflection.getField(pinIndex)->getVisibility(), RenderPassReflection::Field::Visibility::Input);
                if (isInput)
                {
                    if (renderPassUI.mReflection.getField(pinIndex)->getName() == currentEdge.dstField)
                    {
                        break;
                    }
                    inputPinIndex++;
                }
                pinIndex++;
            }

            auto pSourceNode = mpRenderGraph->mNodeData.find(mpRenderGraph->mpGraph->getEdge(edgeID)->getSourceNode());
            FALCOR_ASSERT(pSourceNode != mpRenderGraph->mNodeData.end());
            addedExecutionInput = currentEdge.dstField.empty();

            std::string dstFieldName = currentEdge.dstField.empty() ? kInPrefix + nameToIndex.first : currentEdge.dstField;
            std::string inputPinString = nameToIndex.first + "." + dstFieldName;
            std::string srcFieldName = currentEdge.srcField.empty() ? kOutPrefix + pSourceNode->second.name : currentEdge.srcField;
            std::string outputPinString = pSourceNode->second.name + "." + srcFieldName;
            if (nodeConnectedInput.find(inputPinString) == nodeConnectedInput.end())
            {
                nodeConnectedInput.insert(inputPinString);
            }

            renderPassUI.addUIPin(dstFieldName, inputPinIndex, true, srcFieldName, pSourceNode->second.name);
            mOutputToInputPins[outputPinString].push_back(std::make_pair(inputPinIndex, renderPassUI.mGuiNodeID));
            mInputPinStringToLinkID.insert(std::make_pair(inputPinString, edgeID));
        }

        // add all of the outgoing connections
        for (uint32_t i = 0; i < pCurrentPass->getOutgoingEdgeCount(); ++i)
        {
            uint32_t edgeID = pCurrentPass->getOutgoingEdge(i);
            auto currentEdge = mpRenderGraph->mEdgeData[edgeID];
            outputPinIndex = 0;

            std::string pinString = nameToIndex.first + "." + currentEdge.srcField;
            if (nodeConnectedOutput.find(pinString) != nodeConnectedOutput.end())
            {
                break;
            }

            bool isGraphOutput = passGraphOutputs.find(currentEdge.srcField) != passGraphOutputs.end();
            uint32_t pinIndex = 0;

            while (pinIndex < renderPassUI.mReflection.getFieldCount())
            {
                bool isOutput =
                    (static_cast<uint32_t>(
                         renderPassUI.mReflection.getField(pinIndex)->getVisibility() & RenderPassReflection::Field::Visibility::Output
                     ) != 0);
                if (isOutput)
                {
                    if (renderPassUI.mReflection.getField(pinIndex)->getName() == currentEdge.srcField)
                    {
                        break;
                    }
                    outputPinIndex++;
                }
                pinIndex++;
            }

            auto pDestNode = mpRenderGraph->mNodeData.find(mpRenderGraph->mpGraph->getEdge(edgeID)->getDestNode());
            FALCOR_ASSERT(pDestNode != mpRenderGraph->mNodeData.end());
            addedExecutionOutput = currentEdge.dstField.empty();

            std::string dstFieldName = currentEdge.dstField.empty() ? kInPrefix + pDestNode->second.name : currentEdge.dstField;
            std::string inputPinString = nameToIndex.first + "." + dstFieldName;
            std::string srcFieldName = currentEdge.srcField.empty() ? kOutPrefix + nameToIndex.first : currentEdge.srcField;
            std::string outputPinString = pDestNode->second.name + "." + srcFieldName;

            renderPassUI.addUIPin(srcFieldName, outputPinIndex, false, dstFieldName, pDestNode->second.name, isGraphOutput);
            nodeConnectedOutput.insert(pinString);
        }

        // Now we know which nodes are connected within the graph and not
        inputPinIndex = 0;
        outputPinIndex = 0;

        for (uint32_t i = 0; i < renderPassUI.mReflection.getFieldCount(); ++i)
        {
            const auto& currentField = *renderPassUI.mReflection.getField(i);

            if (is_set(currentField.getVisibility(), RenderPassReflection::Field::Visibility::Input))
            {
                if (nodeConnectedInput.find(nameToIndex.first + "." + currentField.getName()) == nodeConnectedInput.end())
                {
                    renderPassUI.addUIPin(currentField.getName(), inputPinIndex, true, "");
                }

                inputPinIndex++;
            }

            if (is_set(currentField.getVisibility(), RenderPassReflection::Field::Visibility::Output))
            {
                if (nodeConnectedOutput.find(nameToIndex.first + "." + currentField.getName()) == nodeConnectedOutput.end())
                {
                    bool isGraphOutput = passGraphOutputs.find(currentField.getName()) != passGraphOutputs.end();
                    renderPassUI.addUIPin(currentField.getName(), outputPinIndex, false, "", "", isGraphOutput);
                }

                outputPinIndex++;
            }
        }

        // unconnected nodes will be renamed when they are connected
        if (!addedExecutionInput)
            renderPassUI.addUIPin(
                kInPrefix + nameToIndex.first, static_cast<uint32_t>(renderPassUI.mInputPins.size()), true, "", "", false
            );
        if (!addedExecutionOutput)
            renderPassUI.addUIPin(
                kOutPrefix + nameToIndex.first, static_cast<uint32_t>(renderPassUI.mOutputPins.size()), false, "", "", false
            );

        mRenderPassUI.emplace(std::make_pair(nameToIndex.first, std::move(renderPassUI)));
    }
}
} // namespace Falcor
